package org.jproxy;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.jproxy.annotations.Service;
import org.jproxy.classloader.ClassLoaderFinder;
import org.jproxy.exception.CannotCastClassException;
import org.jproxy.exception.IncompatibleTypeException;
import org.jproxy.exception.JProxyException;
import org.jproxy.exception.MethodNotFoundException;
import org.jproxy.exception.ServiceNotFoundException;
import org.jproxy.json.JSONAttribute;
import org.jproxy.json.JSONBuilder;
import org.jproxy.json.JSONObject;
import org.jproxy.json.JSONParser;
import org.jproxy.json.JSONObject.Type;


class RequestProcessor
{
	private JSONParser parser;
	private JSONBuilder builder;
	private Map<String, InstanceInfo> instanceMap;

	RequestProcessor()
	{
		parser = new JSONParser();
		builder = new JSONBuilder();
		instanceMap = initInstanceMap();
	}

	String process(String className, String json)
	{
		InstanceInfo info = getInstanceInfo(className);
		if(info == null)
			throw new IllegalArgumentException(new StringBuilder("Instance not found:").append(className).toString());
		JSONObject object = parser.parse(json);
		JSONAttribute method = object.getAttribute("method");
		JSONAttribute attributeArgs = object.getAttribute("args");
		JSONObject args = attributeArgs != null ? attributeArgs.getJSONObject() != null ? attributeArgs.getJSONObject() : parser.parse(attributeArgs.getValue()) : null;
		return process(info, method.getJSONObject().toObject(String.class), args);
	}

	private String process(InstanceInfo info, String methodName, JSONObject json)
	{
		for(Method method : info.getMethodList(methodName))
			try
			{
				Class<?> returnType = method.getReturnType();
				return builder.toJSON(process(info.getInstance(), method, json), returnType, extractJSONType(returnType));
			}catch(IncompatibleTypeException e)
			{
//				System.out.println("method signature doesnt match...");
			}catch(CannotCastClassException e)
			{
//				System.out.println("cannot cast objet...");
			}
		throw new MethodNotFoundException(new StringBuilder("Json: [").append(json.toString()).append("]").toString(), methodName);
	}

	private Object process(Object instance, Method method, JSONObject json)
	{
		Object result = null;
		try
		{
			Class<?>[] classArray = method.getParameterTypes();
			if(classArray.length == 0 && json == null)
				result = method.invoke(instance, new Object[]{});
			else if(json != null)
				result = method.invoke(instance, makeArgsArray(classArray, json));
			else
				throw new IncompatibleTypeException();
		}catch(RuntimeException e)
		{
			throw e;
		}catch(Throwable t)
		{
			throw new JProxyException(t);	//TODO ROd -> criar exception especial...
		}
		return result;
	}

	private Object[] makeArgsArray(Class<?>[] classArray, JSONObject json)
	{
		Object[] args = new Object[classArray.length];
		for(int i = 0; i < classArray.length; i++)
		{
			Class<?> clazz = classArray[i];
			if(extractJSONType(clazz).equals(json.getType()))
				args[i] = json.toObject(clazz);
			else
				throw new IncompatibleTypeException();
		}
		return args;
	}

	private Type extractJSONType(Class<?> clazz)
	{
		Type type = Type.VOID;
		if(String.class.equals(clazz))
			type = Type.STRING;
		else if(isNumber(clazz))
			type = Type.NUMBER;
		else if(Boolean.class.equals(clazz) || boolean.class.equals(clazz))
			type = Type.BOOLEAN;
		else if(clazz.isArray())
			type = Type.ARRAY;
		else if(!clazz.isPrimitive())
			type = Type.OBJECT;
		return type;
	}

	private boolean isNumber(Class<?> clazz)
	{
		return int.class.equals(clazz) || Integer.class.equals(clazz) || long.class.equals(clazz) || Long.class.equals(clazz) || float.class.equals(clazz) || Float.class.equals(clazz) || double.class.equals(clazz) || Double.class.equals(clazz);
	}

	//---------------->instances<----------------
	@SuppressWarnings("unchecked")
	private Map<String, InstanceInfo> initInstanceMap()
	{
		ClassLoaderFinder finder = new ClassLoaderFinder();
		List<Class<?>> classList = finder.findClasses(Service.class);
		Map<String, InstanceInfo> map = new HashMap<String, InstanceInfo>(classList.size());
		for(Class<?> clazz : classList)
			try
			{
				InstanceInfo info = new InstanceInfo(clazz.newInstance());
				map.put(clazz.getName(), info);
				Service service = clazz.getAnnotation(Service.class);
				if(service.bindName().length() > 0)
					map.put(service.bindName(), info);
			}catch(Throwable t)
			{
				//TODO ROd -> tratar...
			}
		return map;
	}

	private InstanceInfo getInstanceInfo(String className)
	{
		InstanceInfo instance = instanceMap.get(className);
		if(instance == null)
			throw new ServiceNotFoundException(className);
		return instance;
	}

}
